package com.star.system.aspect;

import cn.hutool.core.util.StrUtil;
import com.star.common.utils.HttpUtil;
import com.star.common.utils.Json;
import com.star.security.context.TokenContextHolder;
import com.star.security.user.Users;
import com.star.system.service.RequestLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;

/**
 * 请求日志记录
 *
 * @author yangzj
 */
@Slf4j
@Component
@Aspect
public class RequestLogAspect {

    @Autowired
    private RequestLogService requestLogService;

    @Around("@annotation(com.star.system.aspect.RequestLog)")
    public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTimeMillis = System.currentTimeMillis();
        Object proceed = joinPoint.proceed();
        this.writeLog(joinPoint, startTimeMillis);
        return proceed;
    }

    /**
     * 写入日志
     *
     * @param joinPoint       连接点
     * @param startTimeMillis 开始时间毫秒
     */
    private void writeLog(ProceedingJoinPoint joinPoint, long startTimeMillis) {
        try {
            int proceedTime = (int) (System.currentTimeMillis() - startTimeMillis);
            // 请求参数
            HttpServletRequest request = HttpUtil.getRequest();
            if (request == null) {
                return;
            }
            com.star.system.entity.RequestLog log = new com.star.system.entity.RequestLog();
            // 请求类型
            Class<?> clazz = joinPoint.getTarget().getClass();
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = clazz.getDeclaredMethod(signature.getName(), signature.getParameterTypes());
            RequestLog requestLog = method.getAnnotation(RequestLog.class);
            if (requestLog == null) {
                return;
            }
           /* String uri = request.getRequestURI();
            String[] uris = uri.split("/");
            uri = uri.replace("/" + uris[1], "");*/
            log.setUserId(getUserId())
                    .setRequestType(requestLog.value().getCode())
                    .setRequestUri(request.getRequestURI())
                    .setRequestBody(this.getParams(request, joinPoint))
                    .setRequestIp(HttpUtil.getIp(request))
                    .setUserAgent(request.getHeader(HttpHeaders.USER_AGENT))
                    .setProceedTime(proceedTime);
            requestLogService.write(log);
        } catch (Exception e) {
            log.error("请求日志切面错误：", e);
        }
    }

    /**
     * 获取用户ID
     *
     * @return {@link Long}
     */
    private Long getUserId() {
        Object principal = TokenContextHolder.getPrincipal();
        if (principal == null) {
            return null;
        }
        return ((Users) principal).getId();
    }

    public static final String IGNORE_KEY = "_t";
    public static final String LOGIN_URI = "login";

    /**
     * 获取参数个数
     * 获取请求参数
     *
     * @param request   HttpServletRequest
     * @param joinPoint 连接点
     * @return Map<String, Object>
     */
    protected String getParams(HttpServletRequest request, ProceedingJoinPoint joinPoint) {
        String method = request.getMethod();
        String uri = request.getRequestURI();
        boolean isPrivacy = StrUtil.isNotBlank(uri) && uri.contains(LOGIN_URI);
        if (RequestMethod.GET.name().equals(method)) {
            Map<String, String[]> parameters = request.getParameterMap();
            if (parameters.isEmpty()) {
                return "";
            }
            Map<String, Object> result = new HashMap<>(16);
            parameters.forEach((key, value) -> {
                if (value.length == 1) {
                    if (!IGNORE_KEY.equals(key)) {
                        result.put(key, value[0]);
                    }
                } else {
                    result.put(key, value);
                }
            });
            return this.subLength(result.toString());
        }
        if (RequestMethod.POST.name().equals(method) || RequestMethod.PUT.name().equals(method)
                || RequestMethod.PATCH.name().equals(method) || RequestMethod.DELETE.name().equals(method)) {
            return this.getPostParams(isPrivacy, joinPoint);
        }
        return "";
    }

    /**
     * 获取解析post入参
     *
     * @param joinPoint 连接点
     * @param isPrivacy 是否隐私
     * @return {@link String}
     */
    private String getPostParams(boolean isPrivacy, JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        if (Objects.isNull(args) || args.length == 0) {
            return "";
        }
        if (args.length == 1) {
            return this.argToString(isPrivacy, args[0]);
        }
        List<Object> needList = new ArrayList<>();
        for (Object value : args) {
            if (value instanceof HttpServletRequestWrapper || value instanceof MultipartFile) {
                continue;
            }
            needList.add(value);
        }
        if (needList.isEmpty()) {
            return "";
        }
        if (needList.size() == 1) {
            return this.argToString(isPrivacy, needList.get(0));
        }
        Map<String, Object> result = new HashMap<>(needList.size());
        for (int i = 0; i < needList.size(); i++) {
            result.put("param" + (i + 1), this.argToString(isPrivacy, args[i]));
        }
        return result.toString();
    }

    public static final String PASSWORD = "password";

    /**
     * arg sto字符串
     *
     * @param obj 对象
     * @return {@link String}
     */
    private String argToString(boolean isPrivacy, Object obj) {
        try {
            if (obj instanceof String || obj instanceof Integer || obj instanceof BigDecimal
                    || obj instanceof Long || obj instanceof Double || obj instanceof Float) {
                return this.subLength(obj.toString());
            }
            String str = Json.toString(obj);
            if (isPrivacy) {
                if (str.contains(PASSWORD)) {
                    Map<Object, Object> map = Json.toMap(str);
                    map.put(PASSWORD, "*");
                    str = Json.toString(map);
                }
            }
            return this.subLength(str);
        } catch (Exception e) {
            return obj.getClass().toString();
        }
    }

    /**
     * 截取最大长度
     */
    public static final int MAX_LENGTH = 255;

    /**
     * 截取长度
     *
     * @param s 年代
     * @return {@link String}
     */
    private String subLength(String s) {
        return s.length() > MAX_LENGTH ? s.substring(0, MAX_LENGTH) + "..." : s;
    }


}
